/**
 * 
 */
package edu.umd.clip.lm.model.training;

import java.util.Arrays;

import com.sleepycat.bind.tuple.*;
import com.sleepycat.collections.StoredMap;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;

import edu.umd.clip.lm.model.data.Context;

public class ContextClusterStorage {
	private static final String CONTEXT_CLUSTER_DB_NAME = "contextclusters";
	private StoredMap<Context,ContextClusters> theMap;
	
	public ContextClusterStorage(Environment env) throws DatabaseException {
		DatabaseConfig dbConf = new DatabaseConfig();
		dbConf.setReadOnly(false);
		dbConf.setAllowCreate(true);
		dbConf.setTransactional(false);
		dbConf.setDeferredWrite(true);

		if (true) {
			try {
				env.truncateDatabase(null, CONTEXT_CLUSTER_DB_NAME, false);
			} catch(DatabaseException e) {}
		}
		
		Database db = env.openDatabase(null, CONTEXT_CLUSTER_DB_NAME, dbConf);
		
		theMap = new StoredMap<Context,ContextClusters>(db, ContextBinding.instance, ContextClustersBinding.instance, true);
	}

	public static class ContextClusters {
		final int clusters[];

		/**
		 * @param clusters
		 */
		public ContextClusters(int[] clusters) {
			this.clusters = clusters;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + Arrays.hashCode(clusters);
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (!(obj instanceof ContextClusters))
				return false;
			ContextClusters other = (ContextClusters) obj;
			if (!Arrays.equals(clusters, other.clusters))
				return false;
			return true;
		}

		public int[] getClusters() {
			return clusters;
		}
	}
	
	static class ContextBinding extends TupleBinding<Context> {
		static final ContextBinding instance = new ContextBinding();
		
		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#entryToObject(com.sleepycat.bind.tuple.TupleInput)
		 */
		@Override
		public Context entryToObject(TupleInput input) {
			byte order = input.readByte();
			Context ctx = new Context(order);
			// NOTE the reverse order to improve sorting
			for(int i=ctx.data.length-1; i>=0; --i) {
				ctx.data[i] = input.readPackedLong();
			}
			return ctx;
		}

		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#objectToEntry(java.lang.Object, com.sleepycat.bind.tuple.TupleOutput)
		 */
		@Override
		public void objectToEntry(Context ctx, TupleOutput output) {
			output.writeByte(ctx.getOrder());
			// NOTE the reverse order to improve sorting
			for(int i=ctx.data.length-1; i>=0; --i) {
				output.writePackedLong(ctx.data[i]);
			}
		}
	}
	
	static class ContextClustersBinding extends TupleBinding<ContextClusters> {
		static final ContextClustersBinding instance = new ContextClustersBinding();
		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#entryToObject(com.sleepycat.bind.tuple.TupleInput)
		 */
		@Override
		public ContextClusters entryToObject(TupleInput input) {
			byte ctxSize = input.readByte();
			int clusters[] = new int[ctxSize];
			for(int i=0; i<ctxSize; ++i) {
				clusters[i] = input.readPackedInt();
			}
			return new ContextClusters(clusters);
		}

		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#objectToEntry(java.lang.Object, com.sleepycat.bind.tuple.TupleOutput)
		 */
		@Override
		public void objectToEntry(ContextClusters clusters, TupleOutput output) {
			output.writeByte(clusters.getClusters().length);
			for(int cluster : clusters.getClusters()) {
				output.writePackedInt(cluster);
			}
		}
	}

	public ContextClusters get(Context key) {
		return theMap.get(key);
	}

	public ContextClusters put(Context key, ContextClusters value) {
		return theMap.put(key, value);
	}

	public ContextClusters putIfAbsent(Context key, ContextClusters value) {
		return theMap.putIfAbsent(key, value);
	}

}